#include "U2EAGLWindow.h"

#include "U2LogManager.h"
#include "U2EAGLSupport.h"
#include "U2EAGLESContext.h"
#include "U2Root.h"
#include "U2WindowEventUtilities.h"
#include "U2GLESPixelUtil.h"
#include "U2GLESRenderSystem.h"
#include "U2Viewport.h"


U2EG_NAMESPACE_USING


EAGLWindow::EAGLWindow(EAGLSupport *glsupport)
: mClosed(false)
, mVisible(false)
, mIsExternal(false)
, mUsingExternalView(false)
, mUsingExternalViewController(false)
, mIsContentScalingSupported(false)
, mContentScalingFactor(1.0)
, mCurrentOSVersion(0.0)
, mGLSupport(glsupport)
, mContext(NULL)
, mWindow(nil)
, mView(nil)
, mViewController(nil)
{
    mIsFullScreen = true;
    mActive = true;
    mHwGamma = false;

    // Check for content scaling.  iOS 4 or later
    mCurrentOSVersion = [[[UIDevice currentDevice] systemVersion] floatValue];
    if(mCurrentOSVersion >= 4.0)
        mIsContentScalingSupported = true;
}

EAGLWindow::~EAGLWindow()
{
    destroy();

    if (mContext == NULL)
    {
        U2_DELETE mContext;
    }

    mContext = NULL;
}

void EAGLWindow::destroy(void)
{
    if (mClosed)
    {
        return;
    }

    mClosed = true;
    mActive = false;

    if (!mIsExternal)
    {
        U2WindowEventUtilities::_removeRenderWindow(this);

        [mWindow release];
        mWindow = nil;
    }

    if (mIsFullScreen)
    {
        switchFullScreen(false);
    }
    
    if(!mUsingExternalView)
        [mView release];

    if(!mUsingExternalViewController)
        [mViewController release];
}

void EAGLWindow::setFullscreen(bool fullscreen, uint width, uint height)
{
}

void EAGLWindow::reposition(int left, int top)
{
}

void EAGLWindow::resize(unsigned int width, unsigned int height)
{
    if(!mWindow) return;
    
    u2real w = mContentScalingFactor, h = mContentScalingFactor;
    
    // Check the orientation of the view controller and adjust dimensions
    if (UIInterfaceOrientationIsPortrait(mViewController.interfaceOrientation))
    {
        h *= std::max(width, height);
        w *= std::min(width, height);
    }
    else
    {
        w *= std::max(width, height);
        h *= std::min(width, height);
    }
    
    // Check if the window size really changed
    if(mWidth == w && mHeight == h)
        return;
    
    // Destroy and recreate the framebuffer with new dimensions 
    mContext->destroyFramebuffer();
    
    mWidth = w;
    mHeight = h;
    
    mContext->createFramebuffer();
    
    for (ViewportList::iterator it = mViewportList.begin(); it != mViewportList.end(); ++it)
    {
        (*it).second->_updateDimensions();
    }
}

void EAGLWindow::windowMovedOrResized()
{
	CGRect frame = [mView frame];
	mWidth = (unsigned int)frame.size.width;
	mHeight = (unsigned int)frame.size.height;
    mLeft = (int)frame.origin.x;
    mTop = (int)frame.origin.y+(int)frame.size.height;

    for (ViewportList::iterator it = mViewportList.begin(); it != mViewportList.end(); ++it)
    {
        (*it).second->_updateDimensions();
    }
}

void EAGLWindow::_beginUpdate(void)
{
    // Call the base class method first
    U2RenderTarget::_beginUpdate();

    if(mContext->mIsMultiSampleSupported && mContext->mNumSamples > 0)
    {
        // Bind the FSAA buffer if we're doing multisampling
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, mContext->mFSAAFramebuffer);
        GL_CHECK_ERROR
    }
}

void EAGLWindow::initNativeCreatedWindow(const NameValuePairList *miscParams)
{
    // This method is called from within create() and after parameters have been parsed.
    // If the window, view or view controller objects are nil at this point, it is safe
    // to assume that external handles are either not being used or are invalid and
    // we can create our own.
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    uint w = 0, h = 0;

    U2ConfigOptionMap::const_iterator opt;
    U2ConfigOptionMap::const_iterator end = mGLSupport->getConfigOptions().end();
    NameValuePairList::const_iterator param;

    if ((opt = mGLSupport->getConfigOptions().find("Video Mode")) != end)
    {
        U2String val = opt->second.currentValue;
        U2String::size_type pos = val.find('x');

        if (pos != U2String::npos)
        {
            w = U2StringConverter::parseUnsignedInt(val.substr(0, pos));
            h = U2StringConverter::parseUnsignedInt(val.substr(pos + 1));
        }
    }

    if ((opt = mGLSupport->getConfigOptions().find("Content Scaling Factor")) != end)
    {
        mContentScalingFactor = U2StringConverter::parseReal(opt->second.currentValue);
    }

    // Set us up with an external window, or create our own.
    if(!mIsExternal)
    {
        mWindow = [[[UIWindow alloc] initWithFrame:CGRectMake(0, 0, w, h)] retain];
    }

    U2Assert(mWindow != nil, "EAGLWindow: Failed to create native window");

    // Set up the view
    if(!mUsingExternalView)
    {
        mView = [[EAGLView alloc] initWithFrame:CGRectMake(0, 0, w, h)];
        mView.opaque = YES;

        // Use the default scale factor of the screen
        // See Apple's documentation on supporting high resolution devices for more info
        mView.contentScaleFactor = mContentScalingFactor;
    }

    U2Assert(mView != nil, "EAGLWindow: Failed to create view");

    [mView setMWindowName:mName];

    U2Assert([mView.layer isKindOfClass:[CAEAGLLayer class]], "EAGLWindow: View's Core Animation layer is not a CAEAGLLayer. This is a requirement for using OpenGL ES for drawing.");
    
    CAEAGLLayer *eaglLayer = (CAEAGLLayer *)mView.layer;
    U2Assert(eaglLayer != nil, "EAGLWindow: Failed to retrieve a pointer to the view's Core Animation layer");

    eaglLayer.opaque = YES;
    eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                    [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking,
                                    kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
    // Set up the view controller
    if(!mUsingExternalViewController)
    {
        mViewController = [[EAGLViewController alloc] init];
    }

    U2Assert(mViewController != nil, "EAGLWindow: Failed to create view controller");
    
    if(mViewController.view != mView)
        mViewController.view = mView;

    CFDictionaryRef dict;   // TODO: Dummy dictionary for now
    if(eaglLayer)
    {
        EAGLSharegroup *group = nil;
        NameValuePairList::const_iterator option;

        if ((option = miscParams->find("externalSharegroup")) != miscParams->end())
        {
            group = (EAGLSharegroup *)U2StringConverter::parseUnsignedLong(option->second);
            U2LogManager::getSingleton().logMessage("iOS: Using an external EAGLSharegroup");
        }

        mContext = mGLSupport->createNewContext(dict, eaglLayer, group);

        mContext->mIsMultiSampleSupported = true;
        mContext->mNumSamples = mFSAA;
    }
    
    U2Assert(mContext != nil, "EAGLWindow: Failed to create OpenGL ES context");

    [mWindow addSubview:mViewController.view];

    mViewController.mGLSupport = mGLSupport;

    if(!mUsingExternalViewController)
        mWindow.rootViewController = mViewController;

    if(!mUsingExternalView)
        [mView release];

    [mWindow makeKeyAndVisible];

    mContext->createFramebuffer();

    // If content scaling is supported, the window size will be smaller than the GL pixel buffer
    // used to render.  Report the buffer size for reference.
    U2StringStream ss;

    ss  << "iOS: Window created " << w << " x " << h
        << " with backing store size " << mContext->mBackingWidth << " x " << mContext->mBackingHeight;
    if(mIsContentScalingSupported)
    {
        ss << " using content scaling factor " << std::fixed << std::setprecision(1) << mContentScalingFactor;
    }
    U2LogManager::getSingleton().logMessage(ss.str());

    [pool release];
}

void EAGLWindow::create(const U2String& name, uint width, uint height,
                            bool fullScreen, const NameValuePairList *miscParams)
{
    short frequency = 0;
    bool vsync = false;
	int left = 0;
	int top  = 0;
    
    mIsFullScreen = fullScreen;
    mName = name;
    mWidth = width;
    mHeight = height;

    if (miscParams)
    {
        NameValuePairList::const_iterator opt;
        NameValuePairList::const_iterator end = miscParams->end();

        // Note: Some platforms support AA inside ordinary windows
        if ((opt = miscParams->find("FSAA")) != end)
        {
            mFSAA = U2StringConverter::parseUnsignedInt(opt->second);
        }
        
        if ((opt = miscParams->find("displayFrequency")) != end)
        {
            frequency = (short)U2StringConverter::parseInt(opt->second);
        }

        if ((opt = miscParams->find("contentScalingFactor")) != end)
        {
            mContentScalingFactor = U2StringConverter::parseReal(opt->second);
        }
        
        if ((opt = miscParams->find("vsync")) != end)
        {
            vsync = U2StringConverter::parseBool(opt->second);
        }

        if ((opt = miscParams->find("left")) != end)
        {
            left = U2StringConverter::parseInt(opt->second);
        }
        
        if ((opt = miscParams->find("top")) != end)
        {
            top = U2StringConverter::parseInt(opt->second);
        }
        
        if ((opt = miscParams->find("title")) != end)
        {
            mName = opt->second;
        }

        if ((opt = miscParams->find("externalWindowHandle")) != end)
        {
            mWindow = (UIWindow *)U2StringConverter::parseUnsignedLong(opt->second);
            mIsExternal = true;
            U2LogManager::getSingleton().logMessage("iOS: Using an external window handle");
        }

        if ((opt = miscParams->find("externalViewHandle")) != end)
        {
            mView = (EAGLView *)U2StringConverter::parseUnsignedLong(opt->second);
            CGRect b = [mView bounds];
            mWidth = b.size.width;
            mHeight = b.size.height;
            mUsingExternalView = true;
            U2LogManager::getSingleton().logMessage("iOS: Using an external view handle");
        }

        if ((opt = miscParams->find("externalViewControllerHandle")) != end)
        {
            mViewController = (EAGLViewController *)U2StringConverter::parseUnsignedLong(opt->second);
            if(mViewController.view != nil)
                mView = (EAGLView *)mViewController.view;
            mUsingExternalViewController = true;
            U2LogManager::getSingleton().logMessage("iOS: Using an external view controller handle");
        }
	}

    initNativeCreatedWindow(miscParams);

    left = top = 0;
	mLeft = left;
	mTop = top;

    // Resize, taking content scaling factor into account
    resize(mWidth, mHeight);

	mActive = true;
	mVisible = true;
	mClosed = false;
}

void EAGLWindow::swapBuffers(bool waitForVSync)
{
    if (mClosed)
    {
        return;
    }

    unsigned int attachmentCount = 0;
    GLenum attachments[3];
    GLESRenderSystem *rs =
        static_cast<GLESRenderSystem*>(U2Root::getSingleton().getRenderSystem());
    unsigned int buffers = rs->getDiscardBuffers();
    
    if(buffers & FBT_COLOUR)
    {
        attachments[attachmentCount++] = GL_COLOR_ATTACHMENT0_OES;
    }
    if(buffers & FBT_DEPTH)
    {
        attachments[attachmentCount++] = GL_DEPTH_ATTACHMENT_OES;
    }
    if(buffers & FBT_STENCIL)
    {
        attachments[attachmentCount++] = GL_STENCIL_ATTACHMENT_OES;
    }
    
    if(mContext->mIsMultiSampleSupported && mContext->mNumSamples > 0)
    {
        glDisable(GL_SCISSOR_TEST);     
        glBindFramebufferOES(GL_READ_FRAMEBUFFER_APPLE, mContext->mFSAAFramebuffer);
        GL_CHECK_ERROR
        glBindFramebufferOES(GL_DRAW_FRAMEBUFFER_APPLE, mContext->mViewFramebuffer);
        GL_CHECK_ERROR
        glResolveMultisampleFramebufferAPPLE();
        GL_CHECK_ERROR
        glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE, attachmentCount, attachments);
        GL_CHECK_ERROR
        
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, mContext->mViewFramebuffer);
        GL_CHECK_ERROR
    }
    else
    {
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, mContext->mViewFramebuffer);
        GL_CHECK_ERROR
        glDiscardFramebufferEXT(GL_FRAMEBUFFER_OES, attachmentCount, attachments);
        GL_CHECK_ERROR
    }
    
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, mContext->mViewRenderbuffer);
    GL_CHECK_ERROR
    if ([mContext->getContext() presentRenderbuffer:GL_RENDERBUFFER_OES] == NO)
    {
        GL_CHECK_ERROR
        U2_EXCEPT(U2Exception::ERR_RENDERINGAPI_ERROR,
                    "Failed to swap buffers in ",
                    __FUNCTION__);
    }
}

void EAGLWindow::getCustomAttribute( const U2String& name, void* pData )
{
	if( name == "GLCONTEXT" )
	{
		*static_cast<EAGLESContext**>(pData) = mContext;
		return;
	}

    if( name == "SHAREGROUP" )
	{
        *(void**)(pData) = mContext->getContext().sharegroup;
        return;
	}

	if( name == "WINDOW" )
	{
		*(void**)(pData) = mWindow;
		return;
	}
    
	if( name == "VIEW" )
	{
		*(void**)(pData) = mViewController.view;
		return;
	}

    if( name == "VIEWCONTROLLER" )
	{
        *(void**)(pData) = mViewController;
        return;
	}
}

void EAGLWindow::copyContentsToMemory(const U2PixelBox &dst, FrameBuffer buffer)
{
    if(dst.format != PF_A8R8G8B8)
        U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS, "Only PF_A8R8G8B8 is a supported format for OpenGL ES", __FUNCTION__);

    if ((dst.right > mWidth) ||
		(dst.bottom > mHeight) ||
		(dst.front != 0) || (dst.back != 1))
	{
		U2_EXCEPT(U2Exception::ERR_INVALIDPARAMS,
                    "Invalid box.",
                    __FUNCTION__ );
	}

	if (buffer == FB_AUTO)
	{
		buffer = mIsFullScreen ? FB_FRONT : FB_BACK;
	}

	// Switch context if different from current one
	U2RenderSystem* rsys = U2Root::getSingleton().getRenderSystem();
	rsys->_setViewport(this->getViewport(0));

    // The following code is adapted from Apple Technical Q & A QA1704
    // http://developer.apple.com/library/ios/#qa/qa1704/_index.html
    NSInteger width = dst.getWidth(), height = dst.getHeight();
    NSInteger dataLength = width * height * 4;
    GLubyte *data = (GLubyte*)malloc(dataLength * sizeof(GLubyte));

    // Read pixel data from the framebuffer
    glPixelStorei(GL_PACK_ALIGNMENT, 4);
    GL_CHECK_ERROR
	glReadPixels((GLint)dst.left, (GLint)dst.top,
                 (GLsizei)dst.getWidth(), (GLsizei)dst.getHeight(),
                 GL_RGBA, GL_UNSIGNED_BYTE, data);
    GL_CHECK_ERROR

    // Create a CGImage with the pixel data
    // If your OpenGL ES content is opaque, use kCGImageAlphaNoneSkipLast to ignore the alpha channel
    // otherwise, use kCGImageAlphaPremultipliedLast
    CGDataProviderRef ref = CGDataProviderCreateWithData(NULL, data, dataLength, NULL);
    CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
    CGImageRef iref = CGImageCreate(width, height, 8, 32, width * 4, colorspace,
                                    kCGBitmapByteOrder32Big | kCGImageAlphaPremultipliedLast,
                                    ref, NULL, true, kCGRenderingIntentDefault);

    // OpenGL ES measures data in PIXELS
    // Create a graphics context with the target size measured in POINTS
    NSInteger widthInPoints = 0, heightInPoints = 0;

    // Set the scale parameter to your OpenGL ES view's contentScaleFactor
    // so that you get a high-resolution snapshot when its value is greater than 1.0
    CGFloat scale = mView.contentScaleFactor;
    widthInPoints = width / scale;
    heightInPoints = height / scale;
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(widthInPoints, heightInPoints), NO, scale);

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextDrawImage(context, CGRectMake(0.0, 0.0, widthInPoints, heightInPoints), iref);

    // Retrieve the UIImage from the current context
    size_t rowSpan = dst.getWidth() * U2PixelUtil::getNumElemBytes(dst.format);
    memcpy(dst.data, CGBitmapContextGetData(context), rowSpan * dst.getHeight());
    UIGraphicsEndImageContext();

    // Clean up
    free(data);
    CFRelease(ref);
    CFRelease(colorspace);
    CGImageRelease(iref);
}

